#!/usr/bin/env python3
# -*- coding: utf-8 -*-

"""
监控代理端
用于收集本机硬件信息并上报到监控系统
"""

import psutil
import platform
import time
import socket
import requests
import json
import argparse
import logging
from typing import Dict, Any, List
import threading

# 配置日志
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s'
)
logger = logging.getLogger(__name__)

class SystemMonitor:
    """系统监控代理类"""
    
    def __init__(self, server_url: str = None, interval: int = 60):
        self.server_url = server_url
        self.hostname = socket.gethostname()
        self.ip_address = self._get_local_ip()
        self.interval = interval
        self.config = {}  # 存储从后端获取的配置
        self.config_lock = threading.Lock()  # 用于线程安全的配置访问
        
    def _get_local_ip(self) -> str:
        """获取主IP地址（用于向后兼容）"""
        try:
            # 创建一个UDP连接来获取本地IP
            s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
            s.connect(("8.8.8.8", 80))
            ip = s.getsockname()[0]
            s.close()
            return ip
        except Exception:
            return "127.0.0.1"
    
    def _get_all_network_interfaces(self) -> List[Dict[str, Any]]:
        """获取所有网络接口及IP地址信息"""
        interfaces = []
        
        try:
            # 获取网络接口信息
            net_if_addrs = psutil.net_if_addrs()
            net_if_stats = psutil.net_if_stats()
            
            for interface_name, addresses in net_if_addrs.items():
                interface_info = {
                    "name": interface_name,
                    "is_up": False,
                    "speed": 0,
                    "mtu": 0,
                    "addresses": []
                }
                
                # 获取接口状态信息
                if interface_name in net_if_stats:
                    stats = net_if_stats[interface_name]
                    interface_info["is_up"] = stats.isup
                    interface_info["speed"] = stats.speed
                    interface_info["mtu"] = stats.mtu
                
                # 获取地址信息
                for addr in addresses:
                    # 将family转换为可读格式
                    family = "Unknown"
                    if addr.family == socket.AF_INET:
                        family = "IPv4"
                    elif addr.family == socket.AF_INET6:
                        family = "IPv6"
                    elif hasattr(socket, 'AF_LINK') and addr.family == socket.AF_LINK:
                        family = "MAC"
                    # 在Windows上，MAC地址的family可能是-1
                    elif addr.family == -1:
                        family = "MAC"
                    
                    address_info = {
                        "family": family,
                        "address": addr.address,
                        "netmask": addr.netmask,
                        "broadcast": addr.broadcast if hasattr(addr, 'broadcast') else None,
                        "peer": addr.peer if hasattr(addr, 'peer') else None
                    }
                    
                    interface_info["addresses"].append(address_info)
                
                interfaces.append(interface_info)
            
            return interfaces
        except Exception as e:
            logger.error(f"获取网络接口信息失败: {e}")
            return []
    
    def get_system_info(self) -> Dict[str, Any]:
        """获取系统基本信息"""
        try:
            return {
                "hostname": self.hostname,
                "ip_address": self.ip_address,
                "network_interfaces": self._get_all_network_interfaces(),
                "os": platform.system(),
                "os_version": platform.version(),
                "platform": platform.platform(),
                "architecture": platform.architecture()[0],
                "machine": platform.machine(),
                "processor": platform.processor()
            }
        except Exception as e:
            logger.error(f"获取系统信息失败: {e}")
            return {}
    
    def get_cpu_info(self) -> Dict[str, Any]:
        """获取CPU信息"""
        try:
            cpu_percent = psutil.cpu_percent(interval=1)
            cpu_count = psutil.cpu_count()
            cpu_count_logical = psutil.cpu_count(logical=True)
            
            # 获取CPU频率
            cpu_freq = psutil.cpu_freq()
            if cpu_freq:
                cpu_freq_info = {
                    "current": cpu_freq.current,
                    "min": cpu_freq.min,
                    "max": cpu_freq.max
                }
            else:
                cpu_freq_info = {}
            
            return {
                "cpu_percent": cpu_percent,
                "cpu_count": cpu_count,
                "cpu_count_logical": cpu_count_logical,
                "cpu_freq": cpu_freq_info
            }
        except Exception as e:
            logger.error(f"获取CPU信息失败: {e}")
            return {}
    
    def get_memory_info(self) -> Dict[str, Any]:
        """获取内存信息"""
        try:
            memory = psutil.virtual_memory()
            swap = psutil.swap_memory()
            
            return {
                "memory_total": memory.total,
                "memory_available": memory.available,
                "memory_percent": memory.percent,
                "memory_used": memory.used,
                "memory_free": memory.free,
                "swap_total": swap.total,
                "swap_used": swap.used,
                "swap_free": swap.free,
                "swap_percent": swap.percent
            }
        except Exception as e:
            logger.error(f"获取内存信息失败: {e}")
            return {}
    
    def get_disk_info(self) -> Dict[str, Any]:
        """获取磁盘信息"""
        try:
            disk_info = []
            partitions = psutil.disk_partitions()
            
            for partition in partitions:
                try:
                    partition_usage = psutil.disk_usage(partition.mountpoint)
                    disk_info.append({
                        "device": partition.device,
                        "mountpoint": partition.mountpoint,
                        "file_system": partition.fstype,
                        "total": partition_usage.total,
                        "used": partition_usage.used,
                        "free": partition_usage.free,
                        "percent": partition_usage.percent
                    })
                except PermissionError:
                    # 某些分区可能没有访问权限
                    continue
            
            return {
                "partitions": disk_info
            }
        except Exception as e:
            logger.error(f"获取磁盘信息失败: {e}")
            return {}
    
    def get_network_info(self) -> Dict[str, Any]:
        """获取网络信息"""
        try:
            network_stats = psutil.net_io_counters()
            
            return {
                "bytes_sent": network_stats.bytes_sent,
                "bytes_recv": network_stats.bytes_recv,
                "packets_sent": network_stats.packets_sent,
                "packets_recv": network_stats.packets_recv,
                "errin": network_stats.errin,
                "errout": network_stats.errout,
                "dropin": network_stats.dropin,
                "dropout": network_stats.dropout
            }
        except Exception as e:
            logger.error(f"获取网络信息失败: {e}")
            return {}
    
    def get_process_info(self) -> List[Dict[str, Any]]:
        """获取进程信息"""
        try:
            processes = []
            for proc in psutil.process_iter(['pid', 'name', 'cpu_percent', 'memory_info', 'username', 'status']):
                try:
                    # 获取进程信息
                    proc_info = proc.info
                    proc_info['memory_mb'] = round(proc_info['memory_info'].rss / (1024 * 1024), 2) if proc_info['memory_info'] else 0
                    
                    # 获取进程监听的端口
                    try:
                        connections = proc.connections()
                        ports = []
                        for conn in connections:
                            if conn.status == 'LISTEN':
                                ports.append(str(conn.laddr.port))
                        proc_info['ports'] = ','.join(ports) if ports else ''
                    except (psutil.AccessDenied, psutil.NoSuchProcess):
                        proc_info['ports'] = ''
                    
                    processes.append(proc_info)
                except (psutil.NoSuchProcess, psutil.AccessDenied, psutil.ZombieProcess):
                    # 进程可能已经结束或者没有权限访问
                    pass
            
            return processes
        except Exception as e:
            logger.error(f"获取进程信息失败: {e}")
            return []
    
    def get_filtered_process_info(self) -> List[Dict[str, Any]]:
        """根据配置获取过滤后的进程信息"""
        # 获取所有进程信息
        all_processes = self.get_process_info()
        
        # 获取配置中的进程名称列表
        with self.config_lock:
            process_names = self.config.get('process_names', [])
            collect_process_info = self.config.get('collect_process_info', False)
        
        # 如果不收集进程信息或者没有指定进程名称，则返回空列表
        if not collect_process_info or not process_names:
            return []
        
        # 过滤进程信息
        filtered_processes = []
        for proc in all_processes:
            # 检查进程名称是否在监控列表中
            if proc['name'] in process_names:
                filtered_processes.append({
                    'pid': proc['pid'],
                    'name': proc['name'],
                    'cpu_percent': proc['cpu_percent'],
                    'memory_mb': proc['memory_mb'],
                    'username': proc['username'],
                    'status': proc['status'],
                    'ports': proc['ports']
                })
        
        return filtered_processes
    
    def get_port_info(self) -> List[Dict[str, Any]]:
        """获取端口信息"""
        try:
            ports = []
            connections = psutil.net_connections(kind='inet')
            
            for conn in connections:
                if conn.status == 'LISTEN':
                    ports.append({
                        'local_address': conn.laddr.ip,
                        'local_port': conn.laddr.port,
                        'status': conn.status,
                        'pid': conn.pid
                    })
            
            return ports
        except Exception as e:
            logger.error(f"获取端口信息失败: {e}")
            return []
    
    def get_filtered_port_info(self) -> List[Dict[str, Any]]:
        """根据配置获取过滤后的端口信息"""
        # 获取所有端口信息
        all_ports = self.get_port_info()
        
        # 获取配置中的端口列表
        with self.config_lock:
            port_list = self.config.get('port_list', [])
            collect_process_info = self.config.get('collect_process_info', False)
        
        # 如果不收集进程信息或者没有指定端口列表，则返回空列表
        if not collect_process_info or not port_list:
            return []
        
        # 过滤端口信息
        filtered_ports = []
        for port_info in all_ports:
            # 检查端口是否在监控列表中
            if port_info['local_port'] in port_list:
                filtered_ports.append(port_info)
        
        return filtered_ports
    
    def collect_all_info(self) -> Dict[str, Any]:
        """收集所有系统信息"""
        logger.info("开始收集系统信息...")
        
        system_info = self.get_system_info()
        cpu_info = self.get_cpu_info()
        memory_info = self.get_memory_info()
        disk_info = self.get_disk_info()
        network_info = self.get_network_info()
        
        # 根据配置决定是否收集进程和端口信息
        with self.config_lock:
            collect_process_info = self.config.get('collect_process_info', False)
        
        process_info = []
        port_info = []
        if collect_process_info:
            process_info = self.get_filtered_process_info()
            port_info = self.get_filtered_port_info()
        
        # 合并所有信息
        all_info = {
            "timestamp": time.time(),
            "system": system_info,
            "cpu": cpu_info,
            "memory": memory_info,
            "disk": disk_info,
            "network": network_info,
            "processes": process_info,
            "ports": port_info
        }
        
        logger.info("系统信息收集完成")
        return all_info
    
    def print_info(self):
        """打印系统信息到控制台"""
        info = self.collect_all_info()
        
        print("=" * 50)
        print("系统监控代理报告")
        print("=" * 50)
        
        # 系统信息
        system = info.get("system", {})
        print(f"主机名: {system.get('hostname', 'N/A')}")
        print(f"主IP地址: {system.get('ip_address', 'N/A')}")
        print(f"操作系统: {system.get('os', 'N/A')} {system.get('os_version', '')}")
        print(f"平台: {system.get('platform', 'N/A')}")
        print(f"架构: {system.get('architecture', 'N/A')}")
        print(f"处理器: {system.get('processor', 'N/A')}")
        
        # 网络接口信息
        network_interfaces = system.get('network_interfaces', [])
        if network_interfaces:
            print("\n网络接口信息:")
            for interface in network_interfaces:
                status = "启用" if interface.get('is_up', False) else "禁用"
                print(f"  {interface.get('name', 'N/A')} ({status}):")
                print(f"    速度: {interface.get('speed', 0)} Mbps")
                print(f"    MTU: {interface.get('mtu', 0)}")
                
                for addr in interface.get('addresses', []):
                    family = addr.get('family', 'N/A')
                    address = addr.get('address', 'N/A')
                    netmask = addr.get('netmask', 'N/A')
                    
                    if family == "IPv4":
                        print(f"    IPv4地址: {address}/{netmask}")
                    elif family == "IPv6":
                        print(f"    IPv6地址: {address}")
                    elif family == "MAC":
                        print(f"    MAC地址: {address}")
        print()
        
        # CPU信息
        cpu = info.get("cpu", {})
        print("CPU信息:")
        print(f"  使用率: {cpu.get('cpu_percent', 'N/A')}%")
        print(f"  物理核心数: {cpu.get('cpu_count', 'N/A')}")
        print(f"  逻辑核心数: {cpu.get('cpu_count_logical', 'N/A')}")
        cpu_freq = cpu.get('cpu_freq', {})
        if cpu_freq:
            print(f"  CPU频率: {cpu_freq.get('current', 'N/A')} MHz")
        print()
        
        # 内存信息
        memory = info.get("memory", {})
        print("内存信息:")
        print(f"  总内存: {self._bytes_to_gb(memory.get('memory_total', 0)):.2f} GB")
        print(f"  已用内存: {self._bytes_to_gb(memory.get('memory_used', 0)):.2f} GB")
        print(f"  可用内存: {self._bytes_to_gb(memory.get('memory_available', 0)):.2f} GB")
        print(f"  内存使用率: {memory.get('memory_percent', 'N/A')}%")
        print(f"  交换空间: {self._bytes_to_gb(memory.get('swap_total', 0)):.2f} GB")
        print(f"  交换使用率: {memory.get('swap_percent', 'N/A')}%")
        print()
        
        # 磁盘信息
        disk = info.get("disk", {})
        print("磁盘信息:")
        for partition in disk.get("partitions", []):
            print(f"  {partition.get('device', 'N/A')} ({partition.get('mountpoint', 'N/A')}):")
            print(f"    文件系统: {partition.get('file_system', 'N/A')}")
            print(f"    总空间: {self._bytes_to_gb(partition.get('total', 0)):.2f} GB")
            print(f"    已用空间: {self._bytes_to_gb(partition.get('used', 0)):.2f} GB")
            print(f"    可用空间: {self._bytes_to_gb(partition.get('free', 0)):.2f} GB")
            print(f"    使用率: {partition.get('percent', 'N/A')}%")
        print()
        
        # 网络信息
        network = info.get("network", {})
        print("网络信息:")
        print(f"  发送字节: {self._bytes_to_mb(network.get('bytes_sent', 0)):.2f} MB")
        print(f"  接收字节: {self._bytes_to_mb(network.get('bytes_recv', 0)):.2f} MB")
        print(f"  发送包数: {network.get('packets_sent', 'N/A')}")
        print(f"  接收包数: {network.get('packets_recv', 'N/A')}")
        print()
        
        # 进程信息
        processes = info.get("processes", [])
        if processes:
            print("进程信息:")
            for proc in processes:
                print(f"  PID: {proc.get('pid', 'N/A')}, "
                      f"名称: {proc.get('name', 'N/A')}, "
                      f"CPU: {proc.get('cpu_percent', 0)}%, "
                      f"内存: {proc.get('memory_mb', 0)}MB, "
                      f"用户: {proc.get('username', 'N/A')}, "
                      f"状态: {proc.get('status', 'N/A')}, "
                      f"端口: {proc.get('ports', '')}")
            print()
        
        # 端口信息
        ports = info.get("ports", [])
        if ports:
            print("端口信息:")
            for port in ports:
                print(f"  地址: {port.get('local_address', 'N/A')}, "
                      f"端口: {port.get('local_port', 'N/A')}, "
                      f"状态: {port.get('status', 'N/A')}, "
                      f"PID: {port.get('pid', 'N/A')}")
            print()
        
        print("=" * 50)
    
    def send_to_server(self, info: Dict[str, Any]) -> bool:
        """发送信息到监控服务器"""
        if not self.server_url:
            logger.warning("未配置服务器URL，跳过发送")
            return False
        
        try:
            headers = {'Content-Type': 'application/json'}
            response = requests.post(
                self.server_url,
                data=json.dumps(info),
                headers=headers,
                timeout=10
            )
            
            if response.status_code == 200:
                logger.info("数据发送成功")
                return True
            else:
                logger.error(f"数据发送失败，服务器地址: {response.url}, 状态码: {response.status_code}")
                return False
        except Exception as e:
            logger.error(f"发送数据到服务器时出错: {e}")
            return False
    
    def fetch_config_from_server(self) -> bool:
        """从监控服务器获取配置"""
        if not self.server_url:
            logger.warning("未配置服务器URL，无法获取配置")
            return False
        
        try:
            # 构造获取配置的URL
            config_url = self.server_url.replace('/metrics/agent-report', '/metrics/agent-config')
            headers = {'Content-Type': 'application/json'}
            
            # 根据IP地址查询机器配置
            params = {'machine_ip': self.ip_address}
            response = requests.get(
                config_url,
                params=params,
                headers=headers,
                timeout=10
            )
            
            if response.status_code == 200:
                data = response.json()
                if data.get('success') and data.get('data'):
                    # 更新配置
                    with self.config_lock:
                        self.config = data['data']
                        logger.info(f"成功获取配置: {self.config}")
                        return True
                else:
                    logger.warning("服务器返回配置数据格式不正确")
            else:
                logger.error(f"获取配置失败，状态码: {response.status_code}")
                return False
        except Exception as e:
            logger.error(f"获取配置时出错: {e}")
            return False
    
    def get_configured_interval(self) -> int:
        """获取配置的监控间隔时间（秒）"""
        with self.config_lock:
            # 默认使用初始化时的间隔时间
            return self.config.get('interval', self.interval)
    
    def _bytes_to_gb(self, bytes_value: int) -> float:
        """将字节转换为GB"""
        return bytes_value / (1024 ** 3)
    
    def _bytes_to_mb(self, bytes_value: int) -> float:
        """将字节转换为MB"""
        return bytes_value / (1024 ** 2)

def config_fetcher(monitor: SystemMonitor, config_interval: int):
    """定时获取配置的线程函数"""
    while True:
        try:
            monitor.fetch_config_from_server()
            time.sleep(config_interval)
        except KeyboardInterrupt:
            logger.info("配置获取线程收到停止信号")
            break
        except Exception as e:
            logger.error(f"获取配置时出错: {e}")
            time.sleep(config_interval)

def main():
    """主函数"""
    parser = argparse.ArgumentParser(description='系统监控代理')
    parser.add_argument('--server-url', default="http://localhost:10001/api/metrics/agent-report", help='监控服务器URL')
    parser.add_argument('--interval', type=int, default=30, help='上报间隔(秒)，默认30秒')
    parser.add_argument('--config-interval', type=int, default=300, help='配置获取间隔(秒)，默认300秒')
    parser.add_argument('--once', action='store_true', help='只执行一次并打印结果')
    
    args = parser.parse_args()
    
    # 如果没有指定服务器URL，使用默认URL
    server_url = args.server_url
    if not server_url and not args.once:
        server_url = "http://localhost:10001/api/metrics/agent-report"
        logger.info(f"使用默认服务器URL: {server_url}")
    
    monitor = SystemMonitor(server_url=server_url, interval=args.interval)
    
    if args.once:
        # 只执行一次并打印结果
        monitor.print_info()
    else:
        # 持续监控并上报
        logger.info(f"开始监控，上报间隔: {args.interval}秒")
        if server_url:
            logger.info(f"服务器URL: {server_url}")
        else:
            logger.warning("未指定服务器URL，将只在本地记录")
        
        # 启动配置获取线程
        config_thread = threading.Thread(
            target=config_fetcher, 
            args=(monitor, args.config_interval),
            daemon=True
        )
        config_thread.start()
        logger.info(f"配置获取线程已启动，获取间隔: {args.config_interval}秒")
        
        # 主监控循环
        while True:
            try:
                monitor_info = monitor.collect_all_info()
                
                # 打印到控制台
                logger.info(f"收集到系统信息:{monitor_info}")
                
                # 发送到服务器
                if server_url:
                    monitor.send_to_server(monitor_info)
                
                # 使用配置的间隔时间或默认间隔时间
                actual_interval = monitor.get_configured_interval()
                logger.info(f"等待 {actual_interval} 秒后进行下次收集")
                
                # 等待下次收集
                time.sleep(actual_interval)
            except KeyboardInterrupt:
                logger.info("收到停止信号，退出监控代理")
                break
            except Exception as e:
                logger.error(f"监控过程中出错: {e}")
                time.sleep(args.interval)

if __name__ == "__main__":
    main()